package de.unikoblenz.west.csxpoi;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
import android.view.View;
import android.widget.ZoomButtonsController;
import android.widget.ZoomButtonsController.OnZoomListener;
import android.widget.ZoomControls;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
import com.google.android.maps.OverlayItem;

import de.unikoblenz.west.csxpoi.R;

/**
 * The view containing an interactive map.
 */
public class InteractiveMapView extends MapView implements OnDoubleTapListener,
		OnGestureListener {

	/**
	 * Delay for new search operation
	 */
	private final static int DURATION = 2000;

	/**
	 * the enclosing activity
	 */
	private static Context mContext = null;

	/**
	 * the map controller for moving and zooming the map
	 */
	private MapController mMapController = null;

	/**
	 * the location overlay displaying the user's current location
	 */
	private MyLocationOverlay mMyLocationOverlay = null;

	/**
	 * the gesture detector for capturing double taps
	 */
	private GestureDetector mGestureDetector = null;

	/**
	 * {@link Handler} for the timer
	 */
	private static Handler mHandler = new Handler();

	/**
	 * the location centered by default (Koblenz)
	 */
	private static final GeoPoint DEFAULT_CENTER = new GeoPoint(50361000,
			7581000);

	/**
	 * the zoom controller of the map
	 */
	private ZoomButtonsController mZoomButtonsController = null;
	
	/**
	 * the default zoom value
	 */
	private static final int DEFAULT_ZOOM = 14;

	/**
	 * the actual zoom value
	 */
	private int actualZoom = DEFAULT_ZOOM;

	/**
	 * the constant describing the request for the POI editor activity
	 */
	private static final int POI_REQUEST = 3;

	/**
	 * the list of POIs shown on the map
	 */
	private List<PoiWrapper> mPois = new ArrayList<PoiWrapper>();

	/**
	 * the overlay displaying the POIs
	 */
	private PoisOverlay mPoisOverlay;


	/**
	 * If this {@link Runnable} is started, the {@link HelpActivity} will fade
	 * out and be invisible.
	 */
	private static Runnable updatePOIsView = new Runnable() {
		public void run() {
			((CsxPoiActivity) mContext).updateSearch();
			mHandler.removeCallbacks(updatePOIsView);
		}
	};
	
	/**
	 * Called when the view is created.
	 * 
	 * @param context
	 *            the enclosing activity
	 * @param attrs
	 *            initial attributes
	 */
	public InteractiveMapView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initialize(context);
	}

	/**
	 * Called when the view is created. k
	 * 
	 * @param context
	 *            the enclosing activity
	 * @param attrs
	 *            initial attributes
	 * @param defStyle
	 *            the default style
	 */
	public InteractiveMapView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initialize(context);
	}

	/**
	 * Called when the view is created.
	 * 
	 * @param context
	 *            the enclosing activity
	 * @param apiKey
	 *            the Google Maps API Key
	 */
	public InteractiveMapView(Context context, String apiKey) {
		super(context, apiKey);
		initialize(context);

	}

	/**
	 * Initializes the map controller, location overlay, gesture detector, and
	 * POIs overlay
	 * 
	 * @param context
	 *            the enclosing activity
	 */
	@SuppressWarnings("deprecation")
	private void initialize(Context context) {
		mContext = context;

		mMapController = getController();
		mMapController.setCenter(DEFAULT_CENTER);
		mMapController.setZoom(DEFAULT_ZOOM);
		setBuiltInZoomControls(true);

		mMyLocationOverlay = new MyLocationOverlay(context, this);
		getOverlays().add(mMyLocationOverlay);

		mGestureDetector = new GestureDetector(this);
		mGestureDetector.setOnDoubleTapListener(this);

		setFocusable(true);
		setFocusableInTouchMode(true);

		Drawable marker = getResources().getDrawable(
				R.drawable.btn_star_big_on_disable);
		marker.setBounds(0, 0, marker.getIntrinsicWidth(),
				marker.getIntrinsicHeight());
		mPoisOverlay = new PoisOverlay(marker);

		getOverlays().add(mPoisOverlay);

		//Add new Listener for Zoom controller to trigger a new search for POIs when zooming out
		try {
			mZoomButtonsController = this.getZoomButtonsController();
			mZoomButtonsController.setOnZoomListener(new OnZoomListener() {

				@Override
				public void onZoom(boolean zoomIn) {
					if (!zoomIn) {
						callSearchTimer(10);
						mMapController.zoomOut();
					}
					else {
						mMapController.zoomIn();
					}

				}

				@Override
				public void onVisibilityChanged(boolean visible) {
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	
	/**
	 * Gets the context of the application
	 * 
	 * @return context
	 */
	public static Context getMapContext() {
		return mContext;
	}

	/**
	 * Sets the POIs displayed on the map
	 * 
	 * @param pois
	 *            a list of POIs to display on the map
	 */
	public void setPois(List<PoiWrapper> pois) {
		mPois = new ArrayList<PoiWrapper>(pois);
		mPoisOverlay.refresh();
		invalidate();
	}

	/**
	 * Updates POIs that are already displayed on the map or adds them if they
	 * are not.
	 * 
	 * @param poi
	 */
	public void updatePoi(PoiWrapper poi) {
		if (mPois.contains(poi)) {
			boolean existing = mPois.get(mPois.indexOf(poi)).selfUpdate();
			if (!existing)
				mPois.remove(poi);
		} else {
			boolean existing = poi.selfUpdate();
			if (existing)
				mPois.add(poi);
		}
		mPoisOverlay.refresh();
		invalidate();
	}

	
	/**
	 * calls the methods to redraw the POIs on the map
	 */
	public void updateOverlayView() {
		if (mPoisOverlay.size() != 0) {
			mPoisOverlay.refresh();
			invalidate();
		}
	}

	/**
	 * Removes all POIs displayed on the map.
	 */
	public void clearPois() {
		try {
			mPois.clear();
			mPoisOverlay.refresh();
			invalidate();
		} catch (UnsupportedOperationException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Animates the map to the specified location.
	 * 
	 * @param location
	 *            the location to animate to
	 */
	public void animateTo(Location location) {
		mMapController.animateTo(new GeoPoint(
				(int) (location.getLatitude() * Constants.MD_FACTOR),
				(int) (location.getLongitude() * Constants.MD_FACTOR)));
	}

	/**
	 * Gets the left boundary of the map (the minimum longitude).
	 * 
	 * @return the left boundary of the map
	 */
	public double getMapLeft() {
		GeoPoint center = getMapCenter();
		double halfLongitudeSpan = getLongitudeSpan() / 2.0;
		return (center.getLongitudeE6() - halfLongitudeSpan)
				/ Constants.MD_FACTOR;
	}

	/**
	 * Gets the right boundary of the map (the maximum longitude).
	 * 
	 * @return the right boundary of the map
	 */
	public double getMapRight() {
		GeoPoint center = getMapCenter();
		double halfLongitudeSpan = getLongitudeSpan() / 2.0;
		return (center.getLongitudeE6() + halfLongitudeSpan)
				/ Constants.MD_FACTOR;
	}

	/**
	 * Gets the top boundary of the map (the maximum latitude).
	 * 
	 * @return the top boundary of the map
	 */
	public double getMapTop() {
		GeoPoint center = getMapCenter();
		double halfLatitudeSpan = getLatitudeSpan() / 2.0;
		return (center.getLatitudeE6() + halfLatitudeSpan)
				/ Constants.MD_FACTOR;
	}

	/**
	 * Gets the bottom boundary of the map (the minimum latitude).
	 * 
	 * @return the bottom boundary of the map
	 */
	public double getMapBottom() {
		GeoPoint center = getMapCenter();
		double halfLatitudeSpan = getLatitudeSpan() / 2.0;
		return (center.getLatitudeE6() - halfLatitudeSpan)
				/ Constants.MD_FACTOR;
	}

	/**
	 * Starts the location updates.
	 */
	protected void resume() {
		if (mMyLocationOverlay != null) {
			mMyLocationOverlay.enableMyLocation();
		}
	}

	/**
	 * Stops the location updates.
	 */
	protected void pause() {
		if (mMyLocationOverlay != null) {
			mMyLocationOverlay.disableMyLocation();
		}
	}

	/**
	 * Called when the map is touched.
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		super.onTouchEvent(event);
		DateSeekBar.setActive();
		HelpButton.setActive();
		return mGestureDetector.onTouchEvent(event);
	}

	/**
	 * Called when the map is tapped twice and zooms in at the tapped location.
	 */
	public boolean onDoubleTap(MotionEvent e) {
		mMapController.animateTo(getProjection().fromPixels((int) e.getX(),
				(int) e.getY()));
		mMapController.zoomIn();
		return true;
	}

	/**
	 * Called when the map is tapped once.
	 */
	public boolean onSingleTapConfirmed(MotionEvent e) {
		return false;
	}

	/**
	 * Called when the map is tapped twice.
	 */
	public boolean onDoubleTapEvent(MotionEvent e) {
		return true;
	}

	/**
	 * Called when the map is tapped and held.
	 */
	public boolean onDown(MotionEvent e) {
		return true;
	}

	/**
	 * Called when a fling is performed on the map.
	 */
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {
		callSearchTimer();
		return true;
	}

	/**
 	* Initializes a new search action with a delay of DURATION
 	*/
	private void callSearchTimer() {
		mHandler.removeCallbacks(updatePOIsView);
		mHandler.postDelayed(updatePOIsView, DURATION);

	}

	/**
	 * Initializes a new search action with a delay of duration
	 * 
	 * @param duration time between instanication and execution of the search request
	 */
	private void callSearchTimer(int duration) {
		mHandler.removeCallbacks(updatePOIsView);
		mHandler.postDelayed(updatePOIsView, duration);
	}

	/**
	 * Called when the map is pressed longer.
	 */
	public void onLongPress(MotionEvent e) {
	}

	/**
	 * Called when the map is scrolled.
	 */
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {
		callSearchTimer();
		return true;
	}

	/**
	 * Called when the map is pressed.
	 */
	public void onShowPress(MotionEvent e) {
		return;
	}

	/**
	 * Called when a single tap is ended.
	 */
	public boolean onSingleTapUp(MotionEvent e) {
		return true;
	}

	/**
	 * An overlay displaying POIs on the map.
	 */
	private class PoisOverlay extends ItemizedOverlay<OverlayItem> {

		/**
		 * the marker for the POIs
		 */
		private Drawable mMarker = null;

		/**
		 * Called when the overlay is created.
		 * 
		 * @param marker
		 *            the marker for the POIs
		 */
		public PoisOverlay(Drawable marker) {
			super(marker);
			mMarker = marker;
			refresh();
		}

		/**
		 * Refreshes the displayed POIs.
		 */
		public void refresh() {
			populate();
			setLastFocusedIndex(-1);
		}

		/**
		 * Called when a POI is added to the map overlay.
		 */
		@Override
		protected OverlayItem createItem(int i) {
			PoiWrapper poi = mPois.get(i);
			System.out.println("POI-Type: " + poi.getType());
			return poi.getOverlayItem();
		}

		/**
		 * Called when the map overlay is drawn.
		 */
		@Override
		public void draw(Canvas canvas, MapView mapView, boolean shadow) {

			boundCenter(mMarker);
			super.draw(canvas, mapView, false);

		}

		/**
		 * Called when a POI is selected and active and then opens the POI
		 * inspector.
		 */
		@Override
		public boolean onTap(int i) {
			if (mPois.get(i).isActive()) {
				Intent poiInspectorIntent = new Intent(mContext,
						PoiInspectorActivity.class);
				poiInspectorIntent.putExtra("user",
						((CsxPoiActivity) mContext).getUserId());
				poiInspectorIntent.putExtra("poi", mPois.get(i));
				((Activity) mContext).startActivityForResult(
						poiInspectorIntent, POI_REQUEST);
			}
			return true;
		}

		/**
		 * Returns the number of POIs in the overlay.
		 */
		@Override
		public int size() {
			return mPois.size();
		}
	}

}
